package cn.dansj.common.utils.json;

import cn.dansj.common.utils.transfer.DateTime;
import cn.dansj.common.utils.transfer.Transformation;
import cn.dansj.common.utils.transfer.Verification;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializable;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.node.EnumNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

public class JSONNode implements JsonSerializable {
    private final JsonNode jsonNode;

    @JsonCreator
    private JSONNode(JsonNode jsonNode) {
        this.jsonNode = jsonNode;
    }

    @JsonCreator
    protected static JSONNode parse(JsonNode jsonNode) {
        return new JSONNode(jsonNode);
    }

    @JsonValue
    protected JsonNode getJsonNode() {
        return jsonNode;
    }

    public Enum<?> toEnum() {
        if (jsonNode instanceof EnumNode) return ((EnumNode) jsonNode).getEnum();
        throw new IllegalArgumentException("JSONNode Not EnumNode");
    }

    public JSONObject toJSONObject() {
        return JSONUtils.toJSONObject(jsonNode);
    }

    private <T> List<T> arrayObjectNodetoList(Class<T> itemClass) {
        List<T> list = new ArrayList<>();
        Iterator<JsonNode> elements = jsonNode.elements();
        while (elements.hasNext()) {
            JsonNode node = elements.next();
            list.add(JSONUtils.toObj(node, itemClass));
        }
        return list;
    }

    private <T> List<T> singleToList(Class<T> itemClass) {
        return new ArrayList<T>() {{
            add(JSONUtils.toObj(jsonNode, itemClass));
        }};
    }

    public <T> List<T> toArrayList(Class<T> itemClass) {
        switch (jsonNode.getNodeType()) {
            case OBJECT:
            case ARRAY:
                return arrayObjectNodetoList(itemClass);
            case BOOLEAN:
            case STRING:
            case NUMBER:
                return singleToList(itemClass);
            default:
                return new ArrayList<>();
        }
    }

    public List<Object> toArrayList() {
        return toArrayList(Object.class);
    }

    public <T extends JsonNode> T deepCopy() {
        return jsonNode.deepCopy();
    }

    public JSONNode get(int i) {
        return JSONNode.parse(jsonNode.get(i));
    }

    public JsonParser traverse() {
        return jsonNode.traverse();
    }

    public JsonParser traverse(ObjectCodec objectCodec) {
        return jsonNode.traverse(objectCodec);
    }

    public JsonNodeType getNodeType() {
        return jsonNode.getNodeType();
    }

    public String toText() {
        String text = jsonNode.asText();
        return Verification.checkNull(text) ? JSONUtils.toJSON(jsonNode) : text;
    }

    public JSONNode findValue(String s) {
        return JSONNode.parse(jsonNode.findValue(s));
    }

    public JSONNode findPath(String s) {
        return JSONNode.parse(jsonNode.findPath(s));
    }

    public JSONNode findParent(String s) {
        return JSONNode.parse(jsonNode.findParent(s));
    }

    public List<JSONNode> findValues(String s, List<JsonNode> list) {
        return jsonNode.findValues(s, list).stream().map(JSONNode::parse).collect(Collectors.toList());
    }

    public List<String> findValuesAsText(String s, List<String> list) {
        return jsonNode.findValuesAsText(s, list);
    }

    public List<JSONNode> findParents(String s, List<JsonNode> list) {
        return jsonNode.findParents(s, list).stream().map(JSONNode::parse).collect(Collectors.toList());
    }

    public String toString() {
        return jsonNode.getNodeType().equals(JsonNodeType.STRING) ? jsonNode.asText() : jsonNode.toString();
    }

    public boolean equals(Object o) {
        if (o instanceof JSONNode) o = ((JSONNode) o).getJsonNode();
        return jsonNode.equals(o);
    }

    public void serialize(JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonNode.serialize(jsonGenerator, serializerProvider);
    }

    public void serializeWithType(JsonGenerator jsonGenerator, SerializerProvider serializerProvider, TypeSerializer typeSerializer) throws IOException {
        jsonNode.serializeWithType(jsonGenerator, serializerProvider, typeSerializer);
    }

    public final JSONNode at(JsonPointer ptr) {
        return JSONNode.parse(jsonNode.at(ptr));
    }

    public final JSONNode at(String jsonPtrExpr) {
        String expr = jsonPtrExpr.replace(".", "/").replaceAll("/+", "/").replaceAll("^/*|/*$", "").trim();
        return JSONNode.parse(jsonNode.at(expr.startsWith("/") ? expr : "/" + expr));
    }

    public int size() {
        return jsonNode.size();
    }

    public boolean isEmpty() {
        return jsonNode.isEmpty();
    }

    public final boolean isValueNode() {
        return jsonNode.isValueNode();
    }

    public final boolean isContainerNode() {
        return jsonNode.isContainerNode();
    }

    public boolean isMissingNode() {
        return jsonNode.isMissingNode();
    }

    public boolean isArray() {
        return jsonNode.isArray();
    }

    public boolean isObject() {
        return jsonNode.isObject();
    }

    public JSONNode get(String fieldName) {
        return JSONNode.parse(jsonNode.get(fieldName));
    }

    public Iterator<String> fieldNames() {
        return jsonNode.fieldNames();
    }

    public final boolean isPojo() {
        return jsonNode.isPojo();
    }

    public final boolean isNumber() {
        return jsonNode.isNumber();
    }

    public boolean isIntegralNumber() {
        return jsonNode.isIntegralNumber();
    }

    public boolean isFloatingPointNumber() {
        return jsonNode.isFloatingPointNumber();
    }

    public boolean isShort() {
        return jsonNode.isShort();
    }

    public boolean isInt() {
        return jsonNode.isInt();
    }

    public boolean isLong() {
        return jsonNode.isLong();
    }

    public boolean isFloat() {
        return jsonNode.isFloat();
    }

    public boolean isDouble() {
        return jsonNode.isDouble();
    }

    public boolean isBigDecimal() {
        return jsonNode.isBigDecimal();
    }

    public boolean isBigInteger() {
        return jsonNode.isBigInteger();
    }

    public final boolean isTextual() {
        return jsonNode.isTextual();
    }

    public final boolean isBoolean() {
        return jsonNode.isBoolean();
    }

    public final boolean isNull() {
        return jsonNode.isNull();
    }

    public final boolean isBinary() {
        return jsonNode.isBinary();
    }

    public boolean canConvertToInt() {
        return jsonNode.canConvertToInt();
    }

    public boolean canConvertToLong() {
        return jsonNode.canConvertToLong() || DateTime.isDateTime(jsonNode.asText());
    }

    public boolean canConvertToExactIntegral() {
        return jsonNode.canConvertToExactIntegral();
    }

    public byte[] toBytes() throws IOException {
        return jsonNode.binaryValue();
    }

    public Number toNumber() {
        return jsonNode.numberValue();
    }

    public short toShort() {
        return jsonNode.shortValue();
    }

    public float toFloat() {
        return jsonNode.floatValue();
    }

    public BigDecimal toDecimal() {
        if (DateTime.isDateTime(jsonNode.asText())) {
            return Transformation.lambdaTryCatch(() -> new BigDecimal(DateTime.getInstance(jsonNode.asText()).getTimeInMillis(), new MathContext(2, RoundingMode.HALF_UP)), jsonNode::decimalValue);
        }
        return jsonNode.decimalValue();
    }

    public BigInteger toBigInteger() {
        if (DateTime.isDateTime(jsonNode.asText())) {
            return Transformation.lambdaTryCatch(() -> BigInteger.valueOf(DateTime.getInstance(jsonNode.asText()).getTimeInMillis()), jsonNode::bigIntegerValue);
        }
        return jsonNode.bigIntegerValue();
    }

    public String toText(String defaultValue) {
        return jsonNode.asText(defaultValue);
    }

    public int toInt() {
        return jsonNode.asInt();
    }

    public int toInt(int defaultValue) {
        return jsonNode.asInt(defaultValue);
    }

    public long toLong() {
        if (DateTime.isDateTime(jsonNode.asText())) {
            return Transformation.lambdaTryCatch(() -> DateTime.getInstance(jsonNode.asText()).getTimeInMillis(), () -> jsonNode.asLong());
        }
        return jsonNode.asLong();
    }

    public long toLong(long defaultValue) {
        if (DateTime.isDateTime(jsonNode.asText())) {
            return Transformation.lambdaTryCatch(() -> DateTime.getInstance(jsonNode.asText()).getTimeInMillis(), () -> jsonNode.asLong(defaultValue));
        }
        return jsonNode.asLong(defaultValue);
    }

    public Date toDate() {
        return DateTime.getInstance(jsonNode.asText()).toDate();
    }

    public Date toDate(Date defaultValue) {
        return Transformation.lambdaTryCatch(() -> JSONUtils.toObj(jsonNode.asText(), Date.class), () -> defaultValue);
    }

    public double toDouble() {
        return jsonNode.asDouble();
    }

    public double toDouble(double defaultValue) {
        return jsonNode.asDouble(defaultValue);
    }

    public boolean toBoolean() {
        return jsonNode.asBoolean();
    }

    public boolean toBoolean(boolean defaultValue) {
        return jsonNode.asBoolean(defaultValue);
    }

    public Object toObject() {
        return JSONUtils.toObj(jsonNode, Object.class);
    }

    public boolean has(String fieldName) {
        return jsonNode.has(fieldName);
    }

    public boolean has(int index) {
        return jsonNode.has(index);
    }

    public boolean hasNonNull(String fieldName) {
        return jsonNode.hasNonNull(fieldName);
    }

    public boolean hasNonNull(int index) {
        return jsonNode.hasNonNull(index);
    }

    public final List<JSONNode> findValues(String fieldName) {
        return jsonNode.findValues(fieldName).stream().map(JSONNode::parse).collect(Collectors.toList());
    }

    public final List<String> findValuesAsText(String fieldName) {
        return jsonNode.findValuesAsText(fieldName);
    }
}
